/*  mbed Microcontroller Library
    Copyright (c) 2006-2013 ARM Limited

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/
#ifndef MBED_SPI_API_H
#define MBED_SPI_API_H

#include "device.h"
#include "dma_api.h"
#include "buffer.h"

#if DEVICE_SPI

#define SPI_EVENT_ERROR       (1 << 1)
#define SPI_EVENT_COMPLETE    (1 << 2)
#define SPI_EVENT_RX_OVERFLOW (1 << 3)
#define SPI_EVENT_ALL         (SPI_EVENT_ERROR | SPI_EVENT_COMPLETE | SPI_EVENT_RX_OVERFLOW)

#define SPI_EVENT_INTERNAL_TRANSFER_COMPLETE (1 << 30) // internal flag to report an event occurred

#define SPI_FILL_WORD         (0xFFFF)

#if DEVICE_SPI_ASYNCH
/** Asynch spi hal structure
*/
typedef struct {
    struct spi_s spi;        /**< Target specific spi structure */
    struct buffer_s tx_buff; /**< Tx buffer */
    struct buffer_s rx_buff; /**< Rx buffer */
} spi_t;

#else
/** Non-asynch spi hal structure
*/
typedef struct spi_s spi_t;

#endif

#ifdef __cplusplus
extern "C" {
#endif

/**
    \defgroup GeneralSPI SPI Configuration Functions
    @{
*/

/** Initialize the SPI peripheral

    Configures the pins used by SPI, sets a default format and frequency, and enables the peripheral
    @param[out] obj  The SPI object to initialize
    @param[in]  mosi The pin to use for MOSI
    @param[in]  miso The pin to use for MISO
    @param[in]  sclk The pin to use for SCLK
    @param[in]  ssel The pin to use for SSEL
*/
void spi_init(spi_t* obj, PinName mosi, PinName miso, PinName sclk, PinName ssel);

/** Release a SPI object

    TODO: spi_free is currently unimplemented
    This will require reference counting at the C++ level to be safe

    Return the pins owned by the SPI object to their reset state
    Disable the SPI peripheral
    Disable the SPI clock
    @param[in] obj The SPI object to deinitialize
*/
void spi_free(spi_t* obj);

/** Configure the SPI format

    Set the number of bits per frame, configure clock polarity and phase, shift order and master/slave mode
    @param[in,out] obj   The SPI object to configure
    @param[in]     bits  The number of bits per frame
    @param[in]     mode  The SPI mode (clock polarity, phase, and shift direction)
    @param[in]     slave Zero for master mode or non-zero for slave mode
*/
void spi_format(spi_t* obj, int bits, int mode, int slave);

/** Set the SPI baud rate

    Actual frequency may differ from the desired frequency due to available dividers and bus clock
    Configures the SPI peripheral's baud rate
    @param[in,out] obj The SPI object to configure
    @param[in]     hz  The baud rate in Hz
*/
void spi_frequency(spi_t* obj, int hz);

/**@}*/
/**
    \defgroup SynchSPI Synchronous SPI Hardware Abstraction Layer
    @{
*/

/** Write a byte out in master mode and receive a value

    @param[in] obj   The SPI peripheral to use for sending
    @param[in] value The value to send
    @return Returns the value received during send
*/
int  spi_master_write(spi_t* obj, int value);

/** Check if a value is available to read

    @param[in] obj The SPI peripheral to check
    @return non-zero if a value is available
*/
int  spi_slave_receive(spi_t* obj);

/** Get a received value out of the SPI receive buffer in slave mode

    Blocks until a value is available
    @param[in] obj The SPI peripheral to read
    @return The value received
*/
int  spi_slave_read(spi_t* obj);

/** Write a value to the SPI peripheral in slave mode

    Blocks until the SPI peripheral can be written to
    @param[in] obj   The SPI peripheral to write
    @param[in] value The value to write
*/
void spi_slave_write(spi_t* obj, int value);

/** Checks if the specified SPI peripheral is in use

    @param[in] obj The SPI peripheral to check
    @return non-zero if the peripheral is currently transmitting
*/
int  spi_busy(spi_t* obj);

/** Get the module number

    @param[in] obj The SPI peripheral to check
    @return The module number
*/
uint8_t spi_get_module(spi_t* obj);

/**@}*/

#if DEVICE_SPI_ASYNCH
/**
    \defgroup AsynchSPI Asynchronous SPI Hardware Abstraction Layer
    @{
*/

/** Begin the SPI transfer. Buffer pointers and lengths are specified in tx_buff and rx_buff

    @param[in] obj       The SPI object which holds the transfer information
    @param[in] tx        The buffer to send
    @param[in] tx_length The number of words to transmit
    @param[in] rx        The buffer to receive
    @param[in] rx_length The number of words to receive
    @param[in] bit_width The bit width of buffer words
    @param[in] event     The logical OR of events to be registered
    @param[in] handler   SPI interrupt handler
    @param[in] hint      A suggestion for how to use DMA with this transfer
*/
void spi_master_transfer(spi_t* obj, const void* tx, size_t tx_length, void* rx, size_t rx_length, uint8_t bit_width,
                         uint32_t handler, uint32_t event, DMAUsage hint);

/** The asynchronous IRQ handler

    Reads the received values out of the RX FIFO, writes values into the TX FIFO and checks for transfer termination
    conditions, such as buffer overflows or transfer complete.
    @param[in] obj     The SPI object which holds the transfer information
    @return event flags if a transfer termination condition was met or 0 otherwise.
*/
uint32_t spi_irq_handler_asynch(spi_t* obj);

/** Attempts to determine if the SPI peripheral is already in use.

    If a temporary DMA channel has been allocated, peripheral is in use.
    If a permanent DMA channel has been allocated, check if the DMA channel is in use.  If not, proceed as though no DMA
    channel were allocated.
    If no DMA channel is allocated, check whether tx and rx buffers have been assigned.  For each assigned buffer, check
    if the corresponding buffer position is less than the buffer length.  If buffers do not indicate activity, check if
    there are any bytes in the FIFOs.
    @param[in] obj The SPI object to check for activity
    @return non-zero if the SPI port is active or zero if it is not.
*/
uint8_t spi_active(spi_t* obj);

/** Abort an SPI transfer

    @param obj The SPI peripheral to stop
*/
void spi_abort_asynch(spi_t* obj);


#endif

/**@}*/

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // SPI_DEVICE

#endif // MBED_SPI_API_H
